iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0
自我挑戰組

程式碼 AI 寫,我負責看懂:30 天拆解 React × TypeScript系列 第 26

Day 26|發現問題後,把修改交給 AI

  • 分享至 

  • xImage
  •  

把 Day 25 發現的問題整理清楚

上一篇進行 Code Review 時,發現 loadItems() 雖然標示會回傳 CollectionEntry[],卻沒有真的檢查 JSON.parse() 還原後的資料格式。

既然已經找到問題,這次就把它整理成比較明確的需求,再交給 AI:

請修改目前的 loadItems(),在 JSON.parse() 後確認資料是陣列,並檢查每一筆資料是否符合 CollectionEntry 的格式。

如果資料格式不正確,請回傳空陣列。不要安裝新的套件,也不要修改其他功能。完成後請說明修改了哪些程式碼及原因。

和一開始只說「幫我做一個收藏清單」相比,這次提出的範圍小了很多,也直接指定要修改的問題與不能更動的內容。

AI 加入了資料檢查

這裡新增一個函式,檢查單筆資料是否符合 CollectionEntry

function isCollectionEntry(
  value: unknown,
): value is CollectionEntry {
  return (
    typeof value === 'object' &&
    value !== null &&
    'id' in value &&
    typeof value.id === 'string' &&
    'title' in value &&
    typeof value.title === 'string' &&
    'type' in value &&
    (value.type === 'manga' || value.type === 'anime') &&
    'status' in value &&
    (
      value.status === 'want' ||
      value.status === 'watching' ||
      value.status === 'completed'
    )
  )
}

雖然只是要檢查資料,但還是突然多了不少東西。
仔細往下看,可以發現它其實是在逐一確認:

  • 資料是不是物件
  • 資料是不是 null
  • idtitle 是否存在,而且是字串
  • type 是否為 mangaanime
  • status 是否為專案允許的其中一種狀態

這些條件都成立時,才會回傳 true

使用 unknown 的原因

函式接收的參數被標示為:

value: unknown

unknown 可以先理解成:

現在還不知道這份資料是什麼,所以使用前必須先檢查。

因為從 localStorage 讀出並經過 JSON.parse() 的內容,不一定符合專案預期,所以不能一開始就直接把它當成 CollectionEntry
使用 unknown 後,TypeScript 會要求程式先透過 typeofnull 判斷與屬性檢查,逐步確認這份資料能不能使用。

value is CollectionEntry 的意思

函式回傳型別寫成:

value is CollectionEntry

這種寫法稱為 Type Predicate,可以先理解成:

如果這個函式回傳 true,TypeScript 就可以把 value 當成 CollectionEntry

它不只是告訴 TypeScript「相信我」,而是搭配函式裡實際執行的條件判斷,檢查資料的內容。
TypeScript 官方文件:使用 Type Predicate 縮小型別

修改後的 loadItems()

有了 isCollectionEntry()loadItems() 就可以改成:

export function loadItems(): CollectionEntry[] {
  try {
    const savedItems = localStorage.getItem(STORAGE_KEY)

    if (!savedItems) return []

    const parsedItems: unknown = JSON.parse(savedItems)

    return Array.isArray(parsedItems) &&
      parsedItems.every(isCollectionEntry)
      ? parsedItems
      : []
  } catch {
    return []
  }
}

這次不會在 JSON.parse() 後直接回傳資料,而是先確認:

Array.isArray(parsedItems)

檢查最外層是不是陣列,再使用:

parsedItems.every(isCollectionEntry)

逐一檢查陣列中的每筆資料。

every() 代表每一筆資料都要通過 isCollectionEntry()。只要其中一筆不符合格式,最後就會回傳空陣列。

修改前後的差別

修改前的流程是:

讀取資料
   ↓
JSON.parse()
   ↓
直接當成 CollectionEntry[] 使用

修改後則多了一層確認:

讀取資料
   ↓
JSON.parse()
   ↓
確認是不是陣列
   ↓
確認每筆資料的格式
   ↓
符合才當成 CollectionEntry[] 使用

原本只是透過 TypeScript 標示預期的型別,現在則真的在程式執行時檢查資料。

AI 修改後還是要自己看

這次已經把問題、修改範圍和預期結果寫得很清楚,但 AI 交出程式碼後,還是不能只看到內容變多就直接接受。
不再只把一句模糊的「幫我修好」交給 AI,而是先根據自己找到的問題,把修改範圍說清楚。
後續也可以視需求,往不同方向確認:

  • 是否只修改了指定的資料讀取部分
  • 每個必要欄位是否都有檢查
  • typestatus 的值是否符合專案定義
  • 正常資料是否仍然能被讀取
  • 錯誤資料是否真的會回傳空陣列
  • Lint 與 TypeScript 檢查是否能通過

今日回顧

  • 發現問題後,可以先整理成明確的需求再交給 AI
  • unknown 表示目前還不能確定資料的型別
  • isCollectionEntry() 會逐一檢查資料需要的欄位
  • value is CollectionEntry 表示通過檢查後,可以將資料當成 CollectionEntry
  • Array.isArray() 檢查最外層是否為陣列
  • every() 確認陣列中的每一筆資料都符合格式
  • AI 完成修改後,仍然需要再次閱讀與驗證

以前交給 AI 的需求比較像是在說自己想要什麼功能,這次則是先找出問題,再限制修改範圍,最後重新理解它給出的解法。雖然修改也交給了 AI,但每當經過修改,是否要直接接受並使用,仍舊不能單純只看它有沒有成功產生程式碼。
下篇見:)


上一篇
Day 25|AI 寫得能跑,就代表寫得好嗎?
下一篇
Day 27|換我檢查 AI 修改後的程式碼
系列文
程式碼 AI 寫,我負責看懂:30 天拆解 React × TypeScript30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言